export default class Snake {
    // 蛇的容器
    element: HTMLElement;
    // 蛇头
    head: HTMLElement;
    // 蛇的身体
    bodies: HTMLCollection;

    constructor() {
        // 这里不加！会ts报错，因为有可能没有#snake这个dom，但我们知道有，通过加一个！阻止报错
        this.element = document.getElementById('snake')!;
        // querySelector方法获取的是一个Element对象，与我们设置的HTMLElement不匹配，所以这里我们使用类型断言as
        this.head = document.querySelector('#snake > div') as HTMLElement;
        this.head.style.background = 'red';
        this.bodies = this.element.getElementsByTagName('div')!;
    }

    // get方法是ts类中的语法糖，你还是可以通过snake1.x的方法获取该属性，但我们可以在这个get方法中写入判断逻辑，将控制权握在开发者手里
    // 获取蛇头的x坐标
    get X() {
        return this.head.offsetLeft;
    }

    get Y() {
        return this.head.offsetTop;
    }

    set X(value: number) {
        if (this.X === value) {
            return;
        }
        // 检查是否掉头
        value = this.checkIsTurn(value, this.X, 'X')
        // 检查是否撞墙
        this.checkWall(value)
        // 移动身体
        this.moveBodies()
        this.head.style.left = value + 'px'
        // 检查是否撞到自己的身体
        this.checkBoomBody(value)
    }

    set Y(value: number) {
        if (this.Y === value) {
            return;
        }
        // 检查是否掉头
        value = this.checkIsTurn(value, this.Y, 'Y')
        // 检查是否撞墙
        this.checkWall(value)
        // 移动身体
        this.moveBodies()
        this.head.style.top = value + 'px'
        // 检查是否撞到自己的身体
        this.checkBoomBody(value)
    }

    // 检查是否撞墙
    checkWall(value:number) {
        if (value < 0 || value > 290) {
            throw new Error('蛇撞墙了！');
        }
    }

    // 检查是否掉头
    checkIsTurn(value:number, currentValue: number, type:string) {
        const firstBody = this.bodies[1] as HTMLElement;
        const position = type === 'Y' ? firstBody?.offsetTop : firstBody?.offsetLeft;
        if (value === position) {
            // 如果发生了掉头，让蛇向反方向继续移动
            if (value > currentValue) {
                value = currentValue - 10
            } else {
                value = currentValue + 10
            }
        }
        return value
    }

    // 检查是否撞到自己的身体 这个方法要在本次蛇的坐标更改后再进行判断
    checkBoomBody(value:number) {
        for(let i = 1; i < this.bodies.length; i++) {
            const bd = this.bodies[i] as HTMLElement
            if (this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
                throw new Error('撞到了自己的身体！');
            }
        }
    }

    addBodies() {
        this.element.insertAdjacentHTML('beforeend', "<div></div>")
    }

    moveBodies() {
        // 后面一格身体的坐标修改为前一节身体的坐标，以此类推
        // 这里for循环的停止条件是i > 0,因为蛇头的位置在外面已经设置了
        for(let i = this.bodies.length - 1; i > 0; i--) {
            // 获取前一格身体的坐标
            let X = (this.bodies[i - 1] as HTMLElement).offsetLeft;
            let Y = (this.bodies[i - 1] as HTMLElement).offsetTop;
            (this.bodies[i] as HTMLElement).style.left = X + 'px';
            (this.bodies[i] as HTMLElement).style.top = Y + 'px';
        }
    }
}